package post_to_in_pkg.parser;
import java.util.Stack;

import post_to_in_pkg.lexer.*;
import post_to_in_pkg.node.*;

import java.io.IOException;
import java.util.ArrayList;
public class Parser {
	
	EnvStack envStack;
	Node root;
	Token plook;
	private Lexer lex;
	
	public Parser(Lexer arg_lex) throws Exception {
		lex=arg_lex; 
		envStack=new EnvStack(null);
		move();	
	}
	

	public void makeSyntaxTree() throws Exception{
		root=block(envStack,getInputSeq());
	}
	
	public OperatorNode MapReduce(OP_Keyword keyword, EnvStack env) throws Exception{
		
		OperatorNode node=new OperatorNode(keyword);
		switch(keyword.toString()){
		
		case "OP_CHECKMULTISIG": case "OP_CHECKMULTISIGVERIFY":	//process variable argc 
		case "OP_CHECKSIG": case "OP_CHECKSIGVERIFY":
		case "OP_HASH160": case "OP_HASH256":
		case "OP_EQUAL": case "OP_EQUALVERIFY":
				if(!keyword.isVariableParameter()){  //process constant argc 
					for(int i=0;i<keyword.getArgmentNumber();i++) {
						if(!env.isEmpty()) {
							OperandNode opnode=env.pop();
							//further estimation
							if(!opnode.isTempNode()){
								opnode.changeToConstStrOpnode();
							}
							node.fillArgument(opnode);
						}
						else {
							throw new IOException("[Script syntax error:] check the word: "
									+"'"+keyword.toString()+"' "+" near line: "+keyword.inline);
						}
					}
				}
				else{								//process variable argc
					for(int argc,list_count=0;list_count<keyword.getArgumentListCount();list_count++){
						OperandNode o=env.pop();
						argc=str2Decimal(o.getToken().toString(),10);
						if(argc!=-1) {
							node.fillArgument(o);
							for(int i=0;i<argc;i++) {
								if(!env.isEmpty()) {
								OperandNode opnode=env.pop();
								//further estimate 
								if(!opnode.isTempNode()){
									opnode.changeToConstStrOpnode();
								}
								node.fillArgument(opnode);
								}
								else {
									throw new IOException("[Parsing error:]"+"Lack of argument, near line: "+keyword.inline);
								}
							}
						}
						else {
							throw new IOException("[Parsing error:]"+"Illegal Argument: "+
									o.getToken().toString()+", near line: "+o.getToken().inline);
						}
						
					}
					try {
					env.pop();	//historical interest, a bug of check multiple signature
					}
					catch(Exception e) {
						throw new IOException("[Historical bug:] please supplement a redundant value before CHECKMULTISIG sentence");
					}
				}
			break;
		case "OP_DUP":
			if(!env.isEmpty()){
				node.fillArgument(env.peek());
			}
			break;

		default:
				for(int i=0;i<keyword.getArgmentNumber();i++) {
					if(!env.isEmpty()) {
						node.fillArgument(env.pop());
					}
					else {
						throw new IOException("[Script syntax error:] check the word: "
								+"'"+keyword.toString()+"' "+" near line: "+keyword.inline);
					}
				}
			
		break;
		}
		return node;

	}
	
	public OperandNode makeNode(Token tok) {
		if(tok.isSaved()){
			return new OperandNode(tok,Type.VOID);
		}
		else if (tok.isOPKey()) {
			return new OperandNode(tok,((OP_Keyword)tok).getReturnType());
		}
		return new OperandNode(tok,Type.VAR);
	}
	
	public void resetParser() {
		
		envStack=null;
		//If_count=0;
		TempNode.resetTempvarCount();
		root=null;
		lex=null;
	}
	
	public Node getSyntaxTree() {
		return root;
	}
	
	private void move() throws Exception{
		try {
		plook=lex.scan();
		}
		catch(IOException e) {
			throw new IOException("[Lexer scanning exception:]"+e.getMessage());
		}
	}
	

	@Deprecated
	private Stack<Node> BifurcateStack(Stack<Node> motherStack) throws Exception{
		
		Object obj=motherStack.clone();
		if(obj instanceof Stack) {
			
		@SuppressWarnings("unchecked")
		Stack<Node> stack=(Stack<Node>)obj;
		return stack;
		}
		else {
			throw new ClassCastException("fail to duplicate stack");
		}
		
	}

	




//a typical process:
/*
 * Merge input
 * Recursive parsing
 * Make node 
 */

public Blocks block(EnvStack env, ArrayList<Token> strSeq) throws Exception{
	Blocks statements=new Blocks();
	Token look=strSeq.remove(0);
	do{
		
		if(look.isSaved()){
			ReservedToken Look=(ReservedToken)look;
			switch(Look.getLabel()){
				case IF: case NOTIF:
					{
						OperandNode Condition=env.pop();
						EnvStack envSavedstk=new EnvStack(env.clone());
						Blocks _if_stmts,_else_stmts;

						ArrayList<Token> _IF_STR_SEQ=trimStrSeq(strSeq,ReservedToken.OP_ENDIF);
						ArrayList<Token> _IF_ELSE_SEQ=trimStrSeq(_IF_STR_SEQ,ReservedToken.OP_ELSE);
						ArrayList<Token> _ENDIF_SEQ=cutSeq(strSeq,_IF_STR_SEQ);

						//IF..ELSE..ENDIF structure
						if(_IF_ELSE_SEQ.size()>1){
							//EnvStack envSavedstk2=new EnvStack(env.clone());
							ArrayList<Token> _ELSE_SEQ=cutSeq(_IF_STR_SEQ,_IF_ELSE_SEQ);
							//optimization can be done later in here
							_IF_ELSE_SEQ.remove(_IF_ELSE_SEQ.size()-1); _ELSE_SEQ.remove(_ELSE_SEQ.size()-1);
							_IF_ELSE_SEQ.addAll(_ENDIF_SEQ); _ELSE_SEQ.addAll(_ENDIF_SEQ);
							_if_stmts=block(envSavedstk,_IF_ELSE_SEQ);
							EnvStack env_else=new EnvStack(env);
							_else_stmts=block(env_else,_ELSE_SEQ);

							If_else_node _if_else_node=new If_else_node(Condition,_if_stmts,_else_stmts);
							statements.born(_if_else_node);
						}
						//IF....ENDIF... structure
						else{
							//create IF_NODE
							if(!(_ENDIF_SEQ.size()>1)) {
								_if_stmts=block(envSavedstk,_IF_STR_SEQ);
								//_else_stmts=block(env,_ENDIF_SEQ);
								If_node _if_node=new If_node(Condition,_if_stmts);
								statements.born(_if_node);
							}
							//create IF_ELSE_NODE
							else {
								//_IF_ELSE_SEQ.remove(_IF_ELSE_SEQ.size()-1);
								//_IF_ELSE_SEQ.addAll(_ENDIF_SEQ);
								_IF_STR_SEQ.remove(_IF_STR_SEQ.size()-1);
								_IF_STR_SEQ.addAll(_ENDIF_SEQ);
								_if_stmts=block(envSavedstk,_IF_STR_SEQ);
								EnvStack env_else=new EnvStack(env);
								_else_stmts=block(env_else,_ENDIF_SEQ);
								If_else_node _if_else_node=new If_else_node(Condition,_if_stmts,_else_stmts);
								statements.born(_if_else_node);
							}
						}

						//reduce env
						while(!env.runtimeStack.isEmpty()) {
							statements.born(env.runtimeStack.remove(0));
							//env.runtimeStack.remove(0);
						}
						return statements; //an endpoint here
				}

				case NOP:				
				break;
				case RET:
					while(!env.runtimeStack.isEmpty()) {
					statements.born(env.runtimeStack.remove(0));
					//env.runtimeStack.remove(0);
					}
					return statements;
				default:
					throw new IOException("[Parsing error:] IllegalReservedTokenException");
					}
			}
		


		else if(look.isOPKey()) {
			OperatorNode OP_Node;
			OP_Node=MapReduce((OP_Keyword)look,env);
			
			//estimate returnType
			if(OP_Node.isTempNode()) {
				env.runtimeStack.push(new TempNode(OP_Node));
			}
			
			if(OP_Node.isReturnVoid()) {
				statements.born(OP_Node);  
			}
		}
		else if(look.isNotNull()){
			//deal with look.tag is Tag.Value
			OperandNode temp_operand_node;
			temp_operand_node=makeNode(look);
			env.runtimeStack.push(temp_operand_node);
		}

		if(!strSeq.isEmpty())
			look=strSeq.remove(0);
	}while(look.isNotNull());

	//reduce the runtime stack 
	while(!env.runtimeStack.isEmpty()) {
		statements.born(env.runtimeStack.remove(0));
		//env.runtimeStack.remove(0);
	}

	return statements;	
}




//return the substring tokens arraylist , this function only means to "if..else" delimiter
private ArrayList<Token> trimStrSeq(ArrayList<Token> strSeq, Token delimiter) throws IOException{

	int pair=1;
	//2 kind of trim IF..ELSE\IF...ENDIF, case'ELSE..ENDIF' can be transformed from previous 2 cases
	int to=0; Token tok;
	if(delimiter.equals(ReservedToken.OP_ENDIF)) {
		for( tok=strSeq.get(to);pair>0;){
			if(tok.equals(ReservedToken.OP_IF))
				pair++;
			else if(tok.equals(delimiter)) {
				pair--;
				if(pair==0)
					break;
			}
			if(to+1<strSeq.size())
				tok=strSeq.get(++to);
			else
				break;
		}

	}
	else {
		pair=0; boolean _else_mark=false;
		for( tok=strSeq.get(to);pair>=0;){
			if(tok.equals(ReservedToken.OP_IF))
				pair++;
			else if(tok.equals(ReservedToken.OP_ENDIF)) {
				pair--;
			}
			if(pair==0&&tok.equals(ReservedToken.OP_ELSE)) {
				_else_mark=true;
				break;
			}
				
			if(to+1<strSeq.size())
				tok=strSeq.get(++to);
			else
				break;
		}
		if(!_else_mark) {
			ArrayList<Token> subSeq=new ArrayList<Token>();
			subSeq.add(Token.Null);
			return subSeq;
		}
		
	}

   

    if(pair!=0){
    		throw new IOException("Missing expected token "+ReservedToken.OP_IF.toString());
    }
    //append "EOF" to the seq for conveniently parsing 

    //notice: subList(inclusive,exclusive)
    //delimiter is not included in subSeq;
    ArrayList<Token> subSeq=new ArrayList<Token>(strSeq.subList(0,to));
  	subSeq.add(Token.Null);
  	return subSeq;
}



private ArrayList<Token> getInputSeq() throws Exception{
	ArrayList<Token> seq;
	for(seq=new ArrayList<Token>(32);plook.isNotNull();move()){
		seq.add(plook);
	}
	seq.add(Token.Null);
	return seq;
}

private ArrayList<Token> cutSeq(ArrayList<Token> be_cutted, ArrayList<Token> cutter) throws Exception{
	int pos=cutter.size();
	if(pos>be_cutted.size())
		throw new Exception("IllegalArgumentException: be_cutted.length < cutter.length");
	else if(pos==be_cutted.size())
		return new ArrayList<Token>();
	return new ArrayList<Token>(be_cutted.subList(pos,be_cutted.size()));
}

private int str2Decimal(String str,int scale) {
	int strlen=str.length();
	int x=0;
	for(int i=0;i<strlen;i++) {
		if(Character.isDigit(str.charAt(i))) {
			x=Character.digit(str.charAt(i), scale)+x*scale;
		}
		else {
			return -1;
		}
	}
	return x;
}

}